﻿using Newtonsoft.Json;

namespace System
{
	/// <summary>
	/// Represents a Time that can be treated as a System.DateTime, a System.TimeSpan or
	/// a string value.
	/// </summary>
	[JsonConverter(typeof(TimeJsonConverter))]
	public struct Time : IComparable, IFormattable, IComparable<Time>, IEquatable<Time>
	{
		private DateTime _innerValue;

		#region Constructors
		/// <summary>
		/// Creates a new instance of System.Time initialize with the specified
		/// System.TimeSpan value.
		/// </summary>
		/// <param name="value">A System.TimeSpan value to initialize this instance of the System.Time object.</param>
		public Time(TimeSpan value)
		{
			_innerValue = DateTime.Now.Date.Add(value);
		}

		/// <summary>
		/// Creates a new instance of System.Time initialize with the specified
		/// System.DateTime value.
		/// </summary>
		/// <param name="value">A System.DateTime value to initialize this instance of the System.Time object.</param>
		public Time(DateTime value)
		{
			_innerValue = value;
		}

		/// <summary>
		/// Creates a new instance of System.Time initialize with the specified
		/// System.String value.
		/// </summary>
		/// <param name="value">A System.String value to initialize this instance of the System.Time object. The format of the string
		/// can be any value that would be recognized by the System.DateTime object.</param>
		public Time(string value)
		{
			Time t = Time.Parse(value);
			_innerValue = t.DateTime;
		}
		#endregion

		#region Public Members
		/// <summary>
		/// Gets the current value as a System.TimeSpan value.
		/// </summary>
		public TimeSpan TimeSpan
		{
			get
			{
				return _innerValue.TimeOfDay;
			}
			set
			{
				_innerValue = _innerValue.Date.Add(value);
			}
		}

		/// <summary>
		/// Gets the current value as a System.DateTime value.
		/// </summary>
		public DateTime DateTime
		{
			get
			{
				return _innerValue;
			}
			set
			{
				_innerValue = value;
			}
		}
		#endregion

		#region Public Overrides
		/// <summary>
		/// Returns the value as a fully formatted string.
		/// </summary>
		public override string ToString()
		{
			// ***
			// *** Long time format
			// ***
			return this.DateTime.ToString("t");
		}

		/// <summary>
		/// Indicates whether this instance and a specified object are equal.
		/// </summary>
		/// <param name="obj">Another object to compare to.</param>
		/// <returns>True if obj and this instance are the same type and represent the same 
		/// value; False otherwise.</returns>
		public override bool Equals(object obj)
		{
			bool returnValue = false;

			if (obj is Time)
			{
				returnValue = this.TimeSpan.Equals(((Time)obj).TimeSpan);
			}

			return returnValue;
		}

		/// <summary>
		/// Returns the hash code for this instance.
		/// </summary>
		/// <returns>A 32-bit signed integer that is the hash code for this instance.</returns>
		public override int GetHashCode()
		{
			return _innerValue.GetHashCode();
		}
		#endregion

		#region Static Members
		/// <summary>
		/// Converts the specified string representation of a time to its System.Time equivalent.
		/// </summary>
		/// <param name="s">A string containing a time to convert.</param>
		/// <returns>An System.Time object that is equivalent to the time contained in s.</returns>
		public static Time Parse(string s)
		{
			Time returnValue = Time.Empty;

			if (!Time.TryParse(s, out returnValue))
			{
				throw new FormatException();
			}

			return returnValue;
		}

		/// <summary>
		/// Converts the specified string representation of a date and time to its System.Time
		/// equivalent and returns a value that indicates whether the conversion succeeded.
		/// </summary>
		/// <param name="s">A string containing a time to convert.</param>
		/// <param name="result">An System.Time object that is equivalent to the time contained in s.</param>
		/// <returns>True if the parameter s was parsed successfully; False otherwise.</returns>
		public static bool TryParse(string s, out Time result)
		{
			bool returnValue = false;
			result = Time.Empty;

			DateTime parsedValue1 = DateTime.MinValue;
			TimeSpan parsedValue2 = TimeSpan.Zero;

			if (DateTime.TryParse(s, out parsedValue1))
			{
				// ***
				// *** Try to parse the time as if it were a
				// *** DateTime string
				// ***
				result = new Time(parsedValue1.TimeOfDay);
				returnValue = true;
			}
			else if (TimeSpan.TryParse(s, out parsedValue2))
			{
				// ***
				// *** Try to parse the time as if it were a
				// *** TimeSpan string
				// ***
				result = new Time(parsedValue2.Duration());
				returnValue = true;
			}

			return returnValue;
		}

		/// <summary>
		/// Determines if the value in the System.String s can be parsed to an
		/// equivalent System.Time object.
		/// </summary>
		/// <param name="s">A string containing a time to convert.</param>
		/// <returns>Returns True if the parameter s can be parsed successfully; False otherwise.</returns>
		public static bool CanParse(string s)
		{
			bool returnValue = false;

			Time parsedResult = Time.Empty;
			returnValue = Time.TryParse(s, out parsedResult);

			return returnValue;
		}

		/// <summary>
		/// Represents the empty System.Time. This field is read-only.
		/// </summary>
		public static readonly Time Empty = new Time();
		#endregion

		#region Implicit Conversions
		/// <summary>
		/// 
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		public static implicit operator string(Time value)
		{
			return value.ToString();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		public static implicit operator Time(string value)
		{
			return new Time(value);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		public static implicit operator DateTime(Time value)
		{
			return value.DateTime;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		public static implicit operator Time(DateTime value)
		{
			return new Time(value);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		public static implicit operator TimeSpan(Time value)
		{
			return value.TimeSpan;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		public static implicit operator Time(TimeSpan value)
		{
			return new Time(value);
		}
		#endregion

		#region Time/Time Operators
		public static Time operator +(Time t1, Time t2)
		{
			Time returnValue = t1;

			if (!object.Equals(t2, null))
			{
				DateTime d = t1.DateTime + t2.TimeSpan;
				returnValue = new Time(d.TimeOfDay);
			}

			return returnValue;
		}

		public static Time operator -(Time t1, Time t2)
		{
			Time returnValue = t1;

			if (!object.Equals(t2, null))
			{
				DateTime d = t1.DateTime - t2.TimeSpan;
				returnValue = new Time(d.TimeOfDay);
			}

			return returnValue;
		}

		public static bool operator ==(Time t1, Time t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (t1.TimeSpan == t2.TimeSpan);
			}

			return returnValue;
		}

		public static bool operator !=(Time t1, Time t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (t1.TimeSpan != t2.TimeSpan);
			}

			return returnValue;
		}

		public static bool operator <(Time t1, Time t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (t1.TimeSpan < t2.TimeSpan);
			}

			return returnValue;
		}

		public static bool operator <=(Time t1, Time t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (t1.TimeSpan <= t2.TimeSpan);
			}

			return returnValue;
		}

		public static bool operator >(Time t1, Time t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (t1.TimeSpan > t2.TimeSpan);
			}

			return returnValue;
		}

		public static bool operator >=(Time t1, Time t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (t1.TimeSpan >= t2.TimeSpan);
			}

			return returnValue;
		}
		#endregion

		#region Time/TimeSpan Operators
		public static Time operator +(Time t1, TimeSpan t2)
		{
			Time returnValue = Time.Empty;

			DateTime d = t1.DateTime + t2;
			returnValue = new Time(d.TimeOfDay);

			return returnValue;
		}

		public static Time operator -(Time t1, TimeSpan t2)
		{
			Time returnValue = Time.Empty;

			DateTime d = t1.DateTime - t2;
			returnValue = new Time(d.TimeOfDay);

			return returnValue;
		}

		public static bool operator ==(Time t1, TimeSpan t2)
		{
			return (t1.TimeSpan == t2);
		}

		public static bool operator !=(Time t1, TimeSpan t2)
		{
			return (t1.TimeSpan != t2);
		}

		public static bool operator <(Time t1, TimeSpan t2)
		{
			return (t1.TimeSpan < t2);
		}

		public static bool operator <=(Time t1, TimeSpan t2)
		{
			return (t1.TimeSpan <= t2);
		}

		public static bool operator >(Time t1, TimeSpan t2)
		{
			return (t1.TimeSpan > t2);
		}

		public static bool operator >=(Time t1, TimeSpan t2)
		{
			return (t1.TimeSpan >= t2);
		}
		#endregion

		#region TimeSpan/Time Operators
		public static Time operator +(TimeSpan t1, Time t2)
		{
			Time returnValue = t1;

			if (!object.Equals(t2, null))
			{
				DateTime d = DateTime.Now.Date.Add(t1) + t2.TimeSpan;
				returnValue = new Time(d.TimeOfDay);
			}

			return returnValue;
		}

		public static Time operator -(TimeSpan t1, Time t2)
		{
			Time returnValue = t1;

			if (!object.Equals(t2, null))
			{
				DateTime d = DateTime.Now.Date.Add(t1) - t2.TimeSpan;
				returnValue = new Time(d.TimeOfDay);
			}

			return returnValue;
		}

		public static bool operator ==(TimeSpan t1, Time t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (t1 == t2.TimeSpan);
			}

			return returnValue;
		}

		public static bool operator !=(TimeSpan t1, Time t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (t1 != t2.TimeSpan);
			}

			return returnValue;
		}

		public static bool operator <(TimeSpan t1, Time t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (t1 < t2.TimeSpan);
			}

			return returnValue;
		}

		public static bool operator <=(TimeSpan t1, Time t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (t1 <= t2.TimeSpan);
			}

			return returnValue;
		}

		public static bool operator >(TimeSpan t1, Time t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (t1 > t2.TimeSpan);
			}

			return returnValue;
		}

		public static bool operator >=(TimeSpan t1, Time t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (t1 >= t2.TimeSpan);
			}

			return returnValue;
		}
		#endregion

		#region Time/DateTime Operators
		public static Time operator +(Time t1, DateTime t2)
		{
			Time returnValue = t1;

			if (!object.Equals(t2, null))
			{
				DateTime d = t1.DateTime + t2.TimeOfDay;
				returnValue = new Time(d.TimeOfDay);
			}

			return returnValue;
		}

		public static Time operator -(Time t1, DateTime t2)
		{
			Time returnValue = t1;

			if (!object.Equals(t2, null))
			{
				DateTime d = t1.DateTime - t2.TimeOfDay;
				returnValue = new Time(d.TimeOfDay);
			}

			return returnValue;
		}

		public static bool operator ==(Time t1, DateTime t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (t1.TimeSpan == t2.TimeOfDay);
			}

			return returnValue;
		}

		public static bool operator !=(Time t1, DateTime t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (t1.TimeSpan != t2.TimeOfDay);
			}

			return returnValue;
		}

		public static bool operator <(Time t1, DateTime t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (t1.TimeSpan < t2.TimeOfDay);
			}

			return returnValue;
		}

		public static bool operator <=(Time t1, DateTime t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (t1.TimeSpan <= t2.TimeOfDay);
			}

			return returnValue;
		}

		public static bool operator >(Time t1, DateTime t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (t1.TimeSpan > t2.TimeOfDay);
			}

			return returnValue;
		}

		public static bool operator >=(Time t1, DateTime t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (t1.TimeSpan >= t2.TimeOfDay);
			}

			return returnValue;
		}
		#endregion

		#region DateTime/Time Operators
		public static Time operator +(DateTime d1, Time t2)
		{
			Time returnValue = d1;

			if (!object.Equals(t2, null))
			{
				DateTime d = d1 + t2.TimeSpan;
				returnValue = new Time(d.TimeOfDay);
			}

			return returnValue;
		}

		public static Time operator -(DateTime d1, Time t2)
		{
			Time returnValue = d1;

			if (!object.Equals(t2, null))
			{
				DateTime d = d1 - t2.TimeSpan;
				returnValue = new Time(d.TimeOfDay);
			}

			return returnValue;
		}

		public static bool operator ==(DateTime d1, Time t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (d1.TimeOfDay == t2.TimeSpan);
			}

			return returnValue;
		}

		public static bool operator !=(DateTime d1, Time t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (d1.TimeOfDay != t2.TimeSpan);
			}

			return returnValue;
		}

		public static bool operator <(DateTime d1, Time t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (d1.TimeOfDay < t2.TimeSpan);
			}

			return returnValue;
		}

		public static bool operator <=(DateTime d1, Time t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (d1.TimeOfDay <= t2.TimeSpan);
			}

			return returnValue;
		}

		public static bool operator >(DateTime d1, Time t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (d1.TimeOfDay > t2.TimeSpan);
			}

			return returnValue;
		}

		public static bool operator >=(DateTime d1, Time t2)
		{
			bool returnValue = false;

			if (!object.Equals(t2, null))
			{
				returnValue = (d1.TimeOfDay >= t2.TimeSpan);
			}

			return returnValue;
		}
		#endregion

		#region IComparable
		/// <summary>
		/// Compares the current instance with another object of the same type and returns
		/// an integer that indicates whether the current instance precedes, follows,
		/// or occurs in the same position in the sort order as the other object.
		/// </summary>
		/// <param name="obj">An object to compare with this instance.</param>
		/// <returns>A value that indicates the relative order of the objects being compared.
		/// The return value has these meanings: Value Meaning Less than zero This instance
		/// precedes obj in the sort order. Zero This instance occurs in the same position
		/// in the sort order as obj. Greater than zero This instance follows obj in
		/// the sort order.</returns>
		public int CompareTo(object obj)
		{
			int returnValue = -1;

			if (obj is Time)
			{
				returnValue = this.TimeSpan.CompareTo(((Time)obj).TimeSpan);
			}

			return returnValue;
		}
		#endregion

		#region IFormattable
		/// <summary>
		/// Converts the value of the current System.Time object to its equivalent
		/// string representation using the specified format.
		/// </summary>
		/// <param name="format">A standard or custom date and time format string.</param>
		/// <returns>A string representation of value of the current System.Time object as
		/// specified by format and provider.</returns>
		public string ToString(string format)
		{
			string returnValue = string.Empty;

			if (string.IsNullOrEmpty(format))
			{
				format = "t";
			}

			returnValue = this.DateTime.ToString(format);

			return returnValue;
		}

		/// <summary>
		/// Converts the value of the current System.Time object to its equivalent
		/// string representation using the specified format and culture-specific format
		/// information.
		/// </summary>
		/// <param name="format">A standard or custom date and time format string.</param>
		/// <param name="formatProvider">An object that supplies culture-specific formatting information.</param>
		/// <returns>A string representation of value of the current System.Time object as
		/// specified by format and provider.</returns>
		public string ToString(string format, IFormatProvider formatProvider)
		{
			string returnValue = string.Empty;

			if (string.IsNullOrEmpty(format))
			{
				format = "t";
			}

			returnValue = this.DateTime.ToString(format, formatProvider);

			return returnValue;
		}
		#endregion

		#region IComparable<Time>
		/// <summary>
		/// Compares the current object with another object of the same type.
		/// </summary>
		/// <param name="other">An object to compare with this object.</param>
		/// <returns>A value that indicates the relative order of the objects being compared.
		/// The return value has the following meanings: Value Meaning Less than zero
		/// This object is less than the other parameter.Zero This object is equal to
		/// other. Greater than zero This object is greater than other.</returns>
		public int CompareTo(Time other)
		{
			return this.TimeSpan.CompareTo(other.TimeSpan);
		}
		#endregion

		#region IEquatable<Time>
		/// <summary>
		/// Indicates whether the current object is equal to another object of the same type.
		/// </summary>
		/// <param name="other">An object to compare with this object.</param>
		/// <returns>True if the current object is equal to the other parameter; False otherwise.</returns>
		public bool Equals(Time other)
		{
			bool returnValue = false;

			returnValue = this.TimeSpan.Equals(other.TimeSpan);

			return returnValue;
		}
		#endregion
	}
}
